package com.hj.excel.config;

import com.hj.excel.annotation.ExcelColumn;
import com.hj.excel.converter.ExcelConverter;
import com.hj.excel.converter.DefaultNullConverter;
import com.hj.excel.exception.ExcelException;
import org.apache.commons.lang3.ClassUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.util.*;

/**
 * @author hj
 * @Date 2022/1/4
 * @Description: 配置类处理工具
 */
public class ColumnConfigUtil {

    private static final Logger logger = LoggerFactory.getLogger(ColumnConfigUtil.class);

    /**
     * 类的列配置
     *
     * @param clazz     类型
     * @param readWrite 解析读(导出)true 解析写(导入)false
     * @return 列配置
     */
    public static List<ColumnConfig<Object, Object>> parseColumnConfig(Class<?> clazz, boolean readWrite) {

        List<ColumnConfig<Object, Object>> columnConfigs = new ArrayList<>();

        try {
            // 获取列配置,所有需要导出的类,最后应该都是从 Object 继承
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz, Object.class);
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            if (null == propertyDescriptors || propertyDescriptors.length == 0) {
                // 至少有一个属性配置
                throw new ExcelException("bean 和其父类, 必须至少包含一个属性");
            }

            // 获取 bean 所有的列配置
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                Method readMethod = propertyDescriptor.getReadMethod();
                Method writeMethod = propertyDescriptor.getWriteMethod();
                String propertyName = propertyDescriptor.getName();
                Class<?> propertyType = propertyDescriptor.getPropertyType();
                if (!typeSupport(propertyType)) {
                    throw new ExcelException("不支持的类型:" + propertyType);
                }
                // 读
                boolean read = null != readMethod && readWrite;
                // 写
                boolean write = null != writeMethod && !readWrite;
                // 只导出属性可读的属性,没有 get 方法的属性不进行导出
                if (read || write) {
                    //先从属性列上获取配置,如果属性列上没有,就从读方法上获取,并覆盖属性列上的配置
                    ColumnConfig<Object, Object> columnConfig = new ColumnConfig<>(propertyName, readMethod, writeMethod);
                    columnConfig.setDataType(propertyType);
                    Field propertyField = null;
                    Class<?> currentClass = clazz;
                    while (currentClass != Object.class && propertyField == null) {
                        try {
                            propertyField = currentClass.getDeclaredField(propertyName);
                        } catch (NoSuchFieldException e) {
                            currentClass = currentClass.getSuperclass();
                        }
                    }
                    if (null == propertyField) {
                        throw new NoSuchFieldException("未找到属性:" + propertyName);
                    }
                    ExcelColumn excelColumn = propertyField.getAnnotation(ExcelColumn.class);
                    if (null != excelColumn) {
                        columnConfig.config(excelColumn.value(), excelColumn.width(), excelColumn.charWidth(),
                                excelColumn.pxWidth(), excelColumn.order(), excelColumn.hidden(),
                                excelColumn.pattern(), excelColumn.chineseWidth(), excelColumn.trim(),
                                excelColumn.cellType(), excelColumn.precision());
                        Class<? extends ExcelConverter<Object, Object>> converter = excelColumn.converter();
                        if (converter != DefaultNullConverter.class) {
                            //检测原类型是否匹配
                            ParameterizedType genericInterface = (ParameterizedType) converter.getGenericInterfaces()[0];
                            Type sourceType = genericInterface.getActualTypeArguments()[0];
                            if (sourceType != propertyType && sourceType != ClassUtils.primitiveToWrapper(propertyType)) {
                                throw new ExcelException(
                                        String.format("转换器[%s]不能匹配目标类型[%s]在列[%s]", converter, propertyType, excelColumn.value())
                                );
                            }

                            try {
                                ExcelConverter<Object, Object> excelConverter = converter.getDeclaredConstructor().newInstance();
                                columnConfig.setExcelConverter(excelConverter);
                            } catch (Exception e) {
                                logger.error("初始化转化器出错，当前列:" + excelColumn.value());
                            }
                        }
                    }
                    // 使用方法上的配置,覆盖属性上的配置
                    ExcelColumn methodExcelColumn;
                    if (readWrite) {
                        //从读方法上覆盖配置
                        methodExcelColumn = readMethod.getAnnotation(ExcelColumn.class);
                    } else {
                        // 从写方法上覆盖配置
                        methodExcelColumn = writeMethod.getAnnotation(ExcelColumn.class);
                    }
                    if (null != methodExcelColumn && null != excelColumn) {
                        columnConfig.config(methodExcelColumn.value(), methodExcelColumn.width(),
                                excelColumn.charWidth(), excelColumn.pxWidth(), methodExcelColumn.order(),
                                methodExcelColumn.hidden(), methodExcelColumn.pattern(), excelColumn.chineseWidth(),
                                excelColumn.trim(), excelColumn.cellType(), excelColumn.precision());
                    }
                    // 只有配置了 ExcelColumn 的才可进行导入导出
                    if (excelColumn != null) {
                        columnConfigs.add(columnConfig);
                    }
                }
            }
            //对导出的属性配置进行排序
            Collections.sort(columnConfigs);
        } catch (ExcelException e) {
            throw e;
        } catch (Exception e) {
            throw new ExcelException("解析列配置出错:" + e.getMessage(), e);
        }
        return columnConfigs;
    }

    /**
     * 支持的类型
     *
     * @param clazz 类型
     * @return 布尔
     */
    private static boolean typeSupport(Class<?> clazz) {
        return clazz.isPrimitive() || clazz == String.class
                || clazz == Integer.class || clazz == Short.class
                || clazz == Long.class || clazz == Float.class
                || clazz == Double.class || clazz == Character.class
                || clazz == Boolean.class || clazz == Byte.class
                || clazz == Date.class;
    }


}
